[アップデート] AWS AppSync が SAM でデプロイ出来るようになりました
いわさです。
AWS SAM は CloudFormation の拡張機能で、サーバーレースアプリケーションでよく使われるリソースを CloudFormation よりも抽象化した拡張コンポーネントで定義出来るようにする機能です。
SAM によって迅速にサーバーレスアプリケーションを構築出来るようにすることを目的にしています。
AWS AppSync は AWS マネージドなサーバーレス GraphQL サービスです。
SAM でサポートされていてもおかしくない AppSync でしたが、これまでは SAM でサポートされていませんでした。
そのため、SAM を使ってサーバーレスアプリケーションを管理したい場合でも、AppSync 関係のリソースについては CloudFormation コンポーネントを使う必要がありました。
しかし、本日のアップデートで AWS SAM で AppSync がサポートされました。
今回のアップデートによって、他の SAM コンポーネントと同様に抽象的な定義が出来るようになるはず。
ただ、よく考えてみたら私はそもそも CloudFormation で AppSync を構築したことがありませんでした。
そこで本日は CloudFormation と SAM で次のような基本的な AppSync + DynamoDB な API を作成し、その特徴を比べてみました。
CloudFormation
出来るだけひとつのテンプレートに今回まとめたかったので、リゾルバーやスキーマなども全部インラインです。
作ってみたところ CloudFormation のテンプレートは次のような感じになりました。
AWSTemplateFormatVersion: '2010-09-09' Resources: MyDynamoDBTable: Type: AWS::DynamoDB::Table Properties: TableName: MyDynamoDBTable AttributeDefinitions: - AttributeName: id AttributeType: S KeySchema: - AttributeName: id KeyType: HASH BillingMode: PAY_PER_REQUEST MyAppSyncApi: Type: AWS::AppSync::GraphQLApi Properties: Name: MyAppSyncApi AuthenticationType: API_KEY MyAppSyncApiKey: Type: AWS::AppSync::ApiKey Properties: ApiId: Fn::GetAtt: - MyAppSyncApi - ApiId MyAppSyncDynamoDBDataSource: Type: AWS::AppSync::DataSource Properties: Name: MyAppSyncDataSource ApiId: Fn::GetAtt: - MyAppSyncApi - ApiId Type: AMAZON_DYNAMODB ServiceRoleArn: Fn::GetAtt: - AppSyncDynamoDBRole - Arn DynamoDBConfig: AwsRegion: !Ref AWS::Region TableName: !Ref MyDynamoDBTable MyAppSyncSchema: Type: AWS::AppSync::GraphQLSchema Properties: ApiId: Fn::GetAtt: - MyAppSyncApi - ApiId Definition: > schema { query: Query mutation: Mutation } type Query { getItem(id: ID!): Item } type Mutation { addItem(id: ID!, hogetext: String!): Item } type Item { id: ID! hogetext: String } GetItemResolver: Type: AWS::AppSync::Resolver DependsOn: MyAppSyncSchema Properties: ApiId: !GetAtt MyAppSyncApi.ApiId TypeName: Query FieldName: getItem DataSourceName: !GetAtt MyAppSyncDynamoDBDataSource.Name RequestMappingTemplate: | { "version" : "2017-02-28", "operation" : "GetItem", "key" : { "id" : $util.dynamodb.toDynamoDBJson($ctx.args.id) } } ResponseMappingTemplate: | $util.toJson($ctx.result) AddItemResolver: Type: AWS::AppSync::Resolver DependsOn: MyAppSyncSchema Properties: ApiId: !GetAtt MyAppSyncApi.ApiId TypeName: Mutation FieldName: addItem DataSourceName: !GetAtt MyAppSyncDynamoDBDataSource.Name RequestMappingTemplate: | { "version": "2017-02-28", "operation": "PutItem", "key": { "id": $util.dynamodb.toDynamoDBJson($ctx.args.id), }, "attributeValues": { "hogetext": $util.dynamodb.toDynamoDBJson($ctx.args.hogetext) } } ResponseMappingTemplate: | { "id": $util.toJson($ctx.args.id), "hogetext": $util.toJson($ctx.args.hogetext) } AppSyncDynamoDBRole: Type: AWS::IAM::Role Properties: RoleName: AppSyncDynamoDBRole AssumeRolePolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Principal: Service: - "appsync.amazonaws.com" Action: - "sts:AssumeRole" Policies: - PolicyName: AppSyncDynamoDBAccessPolicy PolicyDocument: Version: '2012-10-17' Statement: - Effect: Allow Action: - "dynamodb:BatchGetItem" - "dynamodb:GetItem" - "dynamodb:Query" - "dynamodb:Scan" - "dynamodb:BatchWriteItem" - "dynamodb:PutItem" - "dynamodb:UpdateItem" - "dynamodb:DeleteItem" Resource: !Sub - "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${MyDynamoDBTable}" - MyDynamoDBTable: !Ref MyDynamoDBTable
作っていて思ったのは、API Gateway とだいぶ似てるなって思いました。
API Gateway と AppSync の共通点として細かいコンポーネントが非常に多いです。
AppSync でいえば、基本的な構成だけで以下をそれぞれ別で定義して参照させる必要があります。(ApiKey あたりは認証方法によりそうだが)
- AWS::AppSync::GraphQLApi
- AWS::AppSync::ApiKey
- AWS::AppSync::DataSource
- AWS::AppSync::GraphQLSchema
- AWS::AppSync::Resolver
バラバラなので依存関係に注意が必要です。
特に、Resolver からスキーマのフィールド名を参照するのですが、リソース参照ではなくて名称で指定しているだけなので、Resolver に DependsOn をつけるとか気を配る必要がありました。
あと、CloudFormation だとやはり IAM はしっかり意識する必要がありますね。
こちらのテンプレートで次のように DynamoDB への項目の書き込みと読み込みが出来ます。
SAM
SAM の場合はAWS::Serverless::GraphQLApi
ひとつで定義出来ます。
定義出来るというか先程バラバラだったものを詰め込む感じではありますが。
AWSTemplateFormatVersion: 2010-09-09 Description: --- Transform: AWS::Serverless-2016-10-31 Resources: MyDynamoDBTable2: Type: AWS::Serverless::SimpleTable Properties: TableName: MyDynamoDBTable2 PrimaryKey: Name: id Type: String MyGraphQLAPI: Type: AWS::Serverless::GraphQLApi Properties: Auth: Type: API_KEY ApiKeys: MyApiKey: Description: my api key DataSources: DynamoDb: ItemsDataSource: TableName: !Ref MyDynamoDBTable2 TableArn: !GetAtt MyDynamoDBTable2.Arn SchemaInline: > schema { query: Query mutation: Mutation } type Query { getItem(id: ID!): Item } type Mutation { addItem(id: ID!, hogetext: String!): Item } type Item { id: ID! hogetext: String } Functions: addItem: Runtime: Name: APPSYNC_JS Version: 1.0.0 DataSource: ItemsDataSource InlineCode: > import { util } from "@aws-appsync/utils"; export function request(ctx) { const { id, hogetext } = ctx.arguments return { operation: "PutItem", key: util.dynamodb.toMapValues({id: id}), attributeValues: util.dynamodb.toMapValues({ hogetext: hogetext }) }; } export function response(ctx) { return ctx.result; } getItem: Runtime: Name: APPSYNC_JS Version: "1.0.0" DataSource: ItemsDataSource InlineCode: > import { util } from "@aws-appsync/utils"; export function request(ctx) { return dynamoDBGetItemRequest({ id: ctx.args.id }); } export function response(ctx) { return ctx.result; } function dynamoDBGetItemRequest(key) { return { operation: "GetItem", key: util.dynamodb.toMapValues(key), }; } Resolvers: Mutation: addItem: Runtime: Name: APPSYNC_JS Version: "1.0.0" Pipeline: - addItem Query: getItem: Runtime: Name: APPSYNC_JS Version: "1.0.0" Pipeline: - getItem
スキーマ定義は CloudFormation のものをそのまま使うことが出来ました。
また、AppSync から DynamoDB へアクセスするためのサービスロールについては次のように自動構成されました。
データソースのプロパティで Connector を使って独自に定義も出来ますが、デフォルトで自動設定してくれるのは便利ですね。
カスタマイズしたい場合は次を参考にしてください。
DynamoDb - AWS Serverless Application Model
CloudFormation と大きく違う点というか、作成していて最初に戸惑ったのはリゾルバーですね。
SAM では JavaScript パイプラインリゾルバーのみサポートされている
SAM の場合はパイプラインリゾルバーのみがサポートされています。
AWS SAM supports JavaScript pipeline resolvers.
AWS::Serverless::GraphQLApi - AWS Serverless Application Model より
以前は Unit Resolver というリゾルバータイプのみが AppSync ではサポートされていて、以前の AppSync では VTL でマッピングテンプレートを定義していることが多いのではないでしょうか。
2022 年 11 月のアップデートでリゾルバーを JavaScript で定義出来るようになりました。
今回作成したテンプレートでは CloudFormation で使っていた VTL をベースに JavaScript パイプラインリゾルバーに変更してみました。
マネジメントコンソール上でも「推奨」という表記があったので、VTL よりもこちらを使いましょうという方針のようですね。
さいごに
本日は AWS AppSync が SAM でデプロイ出来るようになったので、使ってみました。
バラバラで依存関係を気にしなければならなかった問題などは気にしなくてよくなりそうです。
また、権限周りがすっきりするのはやはり SAM のひとつのメリットかなと思いました。カスタム定義したい場合も Connector で抽象化した定義が可能なので。
AppSync リソース自体は、AWS::Serverless::GraphQLApi
自体に明示的に指定する必要のある必須プロパティが多いので、CloudFormation と比べて記述量はあまり変わってないですね。
AppSync は理解しておかなければいけない概念が元々多いほうだと思いますが、それらが不要になって単純になったというわけではなさそうです。
JavaScript パイプラインリゾルバーの兼ね合いで CloudFormation から SAM へ、必ずしも単純に移行出来るわけではないという点は注意しておきたいですね。